#pragma once
#ifndef LIT__SCRIPT_PREREQS_HH__
#define LIT__SCRIPT_PREREQS_HH__

#include <LitPrerequisites.h>
#include <angelscript.h>
#include <scriptstdstring/scriptstdstring.h>
#include <scriptarray/scriptarray.h>
#include <scriptbuilder/scriptbuilder.h>
#include <scriptany/scriptany.h>
#include <LitExceptions.h>

namespace Lit
{
	enum ScriptModuleFlags
	{
		SMF_ALWAYS_CREATE = asGM_ALWAYS_CREATE,
		SMF_CREATE_IF_NOT_EXISTS = asGM_CREATE_IF_NOT_EXISTS,
		SMF_ONLY_IF_EXISTS = asGM_ONLY_IF_EXISTS
	};

	enum ScriptContextState
	{
		//The context has successfully completed the execution.
		SCS_EXECUTION_FINISHED = asEXECUTION_FINISHED,
		//The execution is suspended and can be resumed.
		SCS_EXECUTION_SUSPENDED = asEXECUTION_SUSPENDED,
		//The execution was aborted by the application.
		SCS_EXECUTION_ABORTED = asEXECUTION_ABORTED,
		//The execution was terminated by an unhandled script exception.
		SCS_EXECUTION_EXCEPTION = asEXECUTION_EXCEPTION,
		//The context has been prepared for a new execution.
		SCS_EXECUTION_PREPARED = asEXECUTION_PREPARED,
		//The context is not initialized.
		SCS_EXECUTION_UNINITIALIZED = asEXECUTION_UNINITIALIZED,
		//The context is currently executing a function call.
		SCS_EXECUTION_ACTIVE = asEXECUTION_ACTIVE,
		//The context has encountered an error and must be reinitialized.
		SCS_EXECUTION_ERROR = asEXECUTION_ERROR,
	};

	typedef asIScriptFunction		ScriptFunction;
	typedef RefPtr<ScriptFunction>	ScriptFunctionPtr;

	class ScriptModule;
	class ScriptContext;
	class ScriptEngine;
	class ScriptPreprocessor;
	class ScriptStream;
	
	typedef RefPtr<ScriptStream>		ScriptStreamPtr;
	typedef RefPtr<ScriptPreprocessor>	ScriptPreprocessorPtr;
	typedef RefPtr<ScriptEngine>		ScriptEnginePtr;
	typedef RefPtr<ScriptContext>		ScriptContextPtr;
	typedef RefPtr<ScriptModule>		ScriptModulePtr;

	
	class NativeScriptAPI;
	class NativeScriptAPICoreFuncs;

	typedef RefPtr<NativeScriptAPI> NativeScriptAPIPtr;
	typedef RefPtr<NativeScriptAPICoreFuncs> NativeScriptAPICoreFuncsPtr;

	
	
	class ScriptEngineError : public LogicError
	{
	protected:
	public:
		ScriptEngineError( const std::string &message, const std::string &source_func,  size_t source_line, const std::string &source_file ) ;
		~ScriptEngineError();
	};
	class ScriptRuntimeError : public RuntimeError
	{
	protected:
		std::string mFunction, 
					mDesc;
		size_t		mLine;
	public:
		ScriptRuntimeError( const std::string& script_function,  size_t script_line, const std::string& script_msg, const std::string &source_func,  size_t source_line, const std::string &source_file );
		~ScriptRuntimeError();

		size_t scriptLine() const;
		const std::string& scriptMessage() const;
		const std::string& scriptFunction() const;
	};
	class ScriptDeclarationError : public ScriptEngineError
	{
	public:
		ScriptDeclarationError( const std::string &decl, int ret_code, const std::string &func, const size_t line, const std::string &file );
	};
	class ScriptNoEntryPoint : public ScriptEngineError
	{
	protected:
	public:
		ScriptNoEntryPoint( 
			const std::string &entry_point, 
			const std::string &source_func,  
			size_t source_line, 
			const std::string &source_file );
		~ScriptNoEntryPoint();
	};
	class ScriptInvalidContext : public ScriptEngineError
	{
	protected:
	public:
		ScriptInvalidContext( 
			const std::string &source_func,  
			size_t source_line, 
			const std::string &source_file );
		~ScriptInvalidContext();
	};
	class ScBase
	{
	public:
		enum CallConvention
		{
			CALL_CDECL            = asCALL_CDECL,
			CALL_STDCALL          = asCALL_STDCALL,
			CALL_THISCALL         = asCALL_THISCALL,
			CALL_CDECL_OBJLAST    = asCALL_CDECL_OBJLAST,
			CALL_CDECL_OBJFIRST   = asCALL_CDECL_OBJFIRST,
			CALL_GENERIC          = asCALL_GENERIC

		};
	};
	template<typename T, bool Pod = true, typename engineT = ScriptEngine>
	class ScType : public ScBase
	{
	protected:
		engineT*		m_eng;
		std::string		m_name;
		asDWORD			m_flags;
	public:
		ScType( engineT * eng, const std::string &alias = typeid(T).name(), asDWORD flags_ = 0, bool create_ = true ) : m_name(alias), m_eng(eng){
			assert(eng);
			m_flags = flags_;
			if( Pod ){ m_flags |= asOBJ_POD; m_flags |= asOBJ_VALUE; }
			if( create_ ){ 
				Ogre::LogManager::getSingleton().logMessage("Type<"+m_name+"> registered", Ogre::LML_NORMAL, true);
				int r = eng->data()->RegisterObjectType( alias.c_str(), sizeof(T), m_flags ); 
				std::string t = ScriptEngine::trReturnCode(r);
				if( r < 0 ) throw ScriptDeclarationError( alias, r, EXC_LOC );
			}
		}
		ScType( const ScType<T, Pod, engineT>& rk ) : m_eng(rk.m_eng), m_name(rk.m_name), m_flags(rk.m_flags){}
		~ScType(){}
		
		template<typename instT> ScType& defVar( const std::string &decl, instT& inst )
		{
			assert(mEng->data()->RegisterObjectProperty( m_name.c_str(), decl.c_str(), asOFFSET( T, inst ) ));
			return *this;
		}

		ScType& defMember( const std::string &decl, asSFuncPtr func, CallConvention callConv = CALL_THISCALL ){ m_eng->data()->RegisterObjectMethod( m_name.c_str(), decl.c_str(), func, callConv ); return *this; }
		ScType& defBehaviour( const std::string &decl, asEBehaviours behaviour, asSFuncPtr func, CallConvention callConv = CALL_CDECL_OBJLAST ){ m_eng->data()->RegisterObjectBehaviour( m_name.c_str(), behaviour, decl.c_str(), func, callConv ); return *this; }
		ScType& defProperty( const std::string &decl, int byte_offset ){ m_eng->data()->RegisterObjectProperty( m_name.c_str(), decl.c_str(), byte_offset ); return *this; }

		ScType& defMember( const std::string &decl, asSFuncPtr func, asDWORD callConv = CALL_THISCALL ){ m_eng->data()->RegisterObjectMethod( m_name.c_str(), decl.c_str(), func, callConv ); return *this; }
		ScType& defBehaviour( const std::string &decl, asEBehaviours behaviour, asSFuncPtr func, asDWORD callConv = CALL_CDECL_OBJLAST ){ m_eng->data()->RegisterObjectBehaviour( m_name.c_str(), behaviour, decl.c_str(), func, callConv ); return *this; }

	};
	template<typename T, bool is_ref_counted = true, typename engineT = ScriptEngine>
	class ScRefType : public ScType<T, false, engineT>
	{
	public:
		ScRefType( engineT * eng, const std::string &alias = typeid(T).name(), asDWORD flags_ = 0, bool create_ = true  )
			: ScType<T, false, engineT>( eng, alias, (is_ref_counted?(asOBJ_REF|flags_):(asOBJ_REF|asOBJ_NOCOUNT|flags_)), false )
		{
			if( create_ ) { 
				Ogre::LogManager::getSingleton().logMessage("RefType<"+m_name+"> registered", Ogre::LML_NORMAL, true);
				assert( eng->data()->RegisterObjectType( alias.c_str(), 0, m_flags ) >= 0 ); 
			}
		}
		ScRefType( const ScRefType<T>& rk ) : ScType<T, false, engineT>(rk){}
		~ScRefType()
		{}
		
		ScRefType<T>& defFactory( asSFuncPtr creator, asSFuncPtr add_ref, asSFuncPtr release_ref, CallConvention creator_conv = CALL_CDECL, CallConvention add_release_conv = CALL_THISCALL){
			return	defCreator( creator, creator_conv )
					.defAddRef( add_ref, add_release_conv )
					.defReleaseRef( release_ref, add_release_conv );
		}
		
		/*	Define the creator for the reference type
				allow scripts to use Self() instead of Other::getSelf()
				in order to get the actual reference
		*/
		ScRefType& defCreator( asSFuncPtr factory, CallConvention callConv = CALL_CDECL){
			// Registering the factory behaviour
			int r = m_eng->data()->RegisterObjectBehaviour(m_name.c_str(), asBEHAVE_FACTORY, (m_name+"@ f()").c_str(), factory, callConv); assert( r >= 0 );
			Ogre::LogManager::getSingleton().logMessage("\tFactory Creator registered for '"+m_name+"'", Ogre::LML_NORMAL, true);
			return *this;
		}
		ScRefType& defAddRef( asSFuncPtr add_ref, CallConvention callConv = CALL_THISCALL){
			int r = m_eng->data()->RegisterObjectBehaviour(m_name.c_str(), asBEHAVE_ADDREF, "void f()", add_ref, callConv); assert( r >= 0 );
			Ogre::LogManager::getSingleton().logMessage("\tFactory method AddRef registered for type '"+m_name+"'", Ogre::LML_NORMAL, true);
			return *this;
		}
		ScRefType& defReleaseRef( asSFuncPtr release_ref, CallConvention callConv = CALL_THISCALL){
			int r = m_eng->data()->RegisterObjectBehaviour(m_name.c_str(), asBEHAVE_RELEASE, "void f()", release_ref, callConv); assert( r >= 0 );
			Ogre::LogManager::getSingleton().logMessage("\tFactory method ReleaseRef registered for type '"+m_name+"'", Ogre::LML_NORMAL, true);
			return *this;
		}
	};
	template<typename T, bool allowHandles = true, bool refCounted = false, typename engineT = ScriptEngine>
	class ScSingletonBase : public ScRefType<T, refCounted, engineT>
	{
	public:
		typename typedef ScSingletonBase<T, allowHandles, refCounted, engineT> Self;

		ScSingletonBase( engineT * eng, const std::string &alias = typeid(T).name(), bool create_=true )
			: ScRefType<T, false, engineT>( eng, alias, allowHandles?0:asOBJ_NOHANDLE, create_ )
		{
			if( create_ ) Ogre::LogManager::getSingleton().logMessage("Singleton<"+m_name+"> registered", Ogre::LML_NORMAL, true);
		}
		ScSingletonBase( const Self& rk ) : ScRefType<T, refCounted, engineT>(rk){}
		~ScSingletonBase()
		{}
		
		static T* _getInstance(){ static T& inst = Singleton<T>::Instance(); return &inst; }

		// Define the accessor for the singleton instance
		Self& defAccessor( asSFuncPtr instance_getter_func = asFUNCTION( Self::_getInstance ), CallConvention callConv = CALL_CDECL ){
			defCreator( instance_getter_func, callConv );
			Ogre::LogManager::getSingleton().logMessage("Singleton<"+m_name+"> Instance Getter registered", Ogre::LML_NORMAL, true);
			return *this;
		}
	};
	template<typename T, bool refCounted = false, typename engineT = ScriptEngine >
	class ScSingleton : public ScSingletonBase<T, true, refCounted, engineT>
	{
	public:
		ScSingleton( engineT * eng, const std::string &alias = typeid(T).name(), bool create_=true ) : ScSingletonBase<T, true, refCounted, engineT>( eng, alias, create_ ){}
		ScSingleton( const ScSingleton<T, refCounted, engineT>& rk ) : ScSingletonBase<T, true, refCounted, engineT>(rk){}
		~ScSingleton(){}
	};
	template<typename T, bool refCounted = false, typename engineT = ScriptEngine >
	class ScSingletonOneRef : public ScSingletonBase<T, false, refCounted, engineT>
	{
	public:
		ScSingletonOneRef( engineT * eng, const std::string &alias = typeid(T).name(), bool create_=true ) : ScSingletonBase<T, false, refCounted, engineT>( eng, alias, create_ ){}
		ScSingletonOneRef( const ScSingletonOneRef<T, refCounted, engineT>& rk ) : ScSingletonBase<T, false, refCounted, engineT>(rk){}
		~ScSingletonOneRef(){}
	};
	template<typename T, typename engineT = ScriptEngine>
	class ScEnum : public ScBase
	{
		engineT * mEng;
		std::string mName;
	public:
		ScEnum( engineT * eng, const std::string &name = typeid(T).name(), bool create = true )	: mEng(eng), mName(name)
		{ 
			assert(eng); 
			if( create ) assert( eng->data()->RegisterEnum(name.c_str()) >= 0 );	
		}
		ScEnum( const ScEnum<T, engineT>& rk ) : mEng(rk.mEng), mName(rk.mName){}
		ScEnum& defValue( T value, const std::string &name ){ assert(mEng->data()->RegisterEnumValue( mName.c_str(), name.c_str(), value )>=0);	return *this; }
	};

	template<typename engineT=ScriptEngine>
	class ScNamespace : public ScBase
	{
		engineT* mEng;
		std::string mName;
	public:
		ScNamespace( const ScNamespace & sp )
		{
			mName=sp.mName; mEng = sp.mEng;
		}
		ScNamespace( engineT*eng, const std::string &child_name, ScNamespace * parent = NULL )
		{
			assert( eng );
			mEng = eng;
			mName = name;
			if( parent )
				switchSpace( parent->current()+"::"+child_name );
			else switchSpace( name );
		}

		ScNamespace enterChild( const std::string &child_name ) const
		{
			return ScNamespace( mEng, child_name, this );
		}
		std::string parentSpace() const
		{
			size_t p = mName.find_last_of(sep);
			std::string sp;
			if( p == std::string::npos ) sp = "";
			else sp = mName.substr( 0, p );
			Ogre::StringUtil::trim(sp);
			return sp;
		}
		std::string currentSpace() const
		{
			return mName;
		}

		ScNamespace& switchSpace( const std::string &name ){ assert( mEng->data()->setNamespace(name.c_str()) >= 0 ); return *this; }
		ScNamespace& leaveSpace(){ return switchSpace( parentSpace() ); }
		ScNamespace& leaveToRoot(){ return switchSpace( "" ); }
	};

	template<typename engineT=ScriptEngine>
	class ScGlobal : public ScBase
	{
		engineT* mEng;
	public:
		ScGlobal( engineT* eng )
		{
			assert(eng); 
			mEng = eng;
		}
		
		ScGlobal& defFunc( const std::string &decl, asSFuncPtr func, CallConvention callConv = CALL_CDECL )
		{
			assert(mEng->data()->RegisterGlobalFunction( decl.c_str(), func, callConv )>=0);
			return *this;
		}
		template<typename instT> ScGlobal& defVar( const std::string &decl, instT & inst )
		{
			assert(mEng->data()->RegisterGlobalProperty( decl.c_str(), (void*)&inst )>=0);
			return *this;
		}
		ScEnum defEnum( const std::string &name ) const{ return ScEnum( mEng, name, true ); }
	};

	class PreprocessorError : public std::runtime_error
	{
	public:
		enum ErrorCode
		{
			PE_INVALID_DIRECTIVE = 1000,
			PE_INVALID_INCLUDE = PE_INVALID_DIRECTIVE + 1,
			PE_INVALID_DEFINE = PE_INVALID_DIRECTIVE + 2
		};
	protected:
		ErrorCode p_code;
		std::string m_err;
		std::string m_nam;
		size_t m_lin;
		ScriptPreprocessorPtr m_proc;
	public:
		PreprocessorError( ScriptPreprocessorPtr proc, const std::string &unit_name, ErrorCode c, const std::string &error, size_t line );
		PreprocessorError( const PreprocessorError& rk );
		~PreprocessorError( );

		const char * what() const;
		
		ScriptPreprocessorPtr getPreprocessor();
		const ScriptPreprocessorPtr getPreprocessor() const;

		std::string getError() const;
		std::string getUnitName() const;
		size_t getLine() const;
		ErrorCode getCode() const;
	};
	namespace PreprocessorErrors
	{
		class InvalidDirective : public PreprocessorError
		{
		public:
			InvalidDirective(ScriptPreprocessorPtr proc, const std::string &directive_, const std::string &unit_name, size_t line) : PreprocessorError( proc, unit_name, PE_INVALID_DIRECTIVE, "Invalid Directive '"+directive_+"'", line ){}
			InvalidDirective(const InvalidDirective& rk) : PreprocessorError(rk){}
		};
		class InvalidInclude : public PreprocessorError
		{
		public:
			InvalidInclude(ScriptPreprocessorPtr proc, const std::string &inc_file, const std::string &unit_name, size_t line) : PreprocessorError( proc, unit_name, PE_INVALID_DIRECTIVE, "Invalid include file '"+inc_file+"'", line ){}
			InvalidInclude(const InvalidInclude& rk) : PreprocessorError(rk){}
		};
		class InvalidDefine : public PreprocessorError
		{
		public:
			InvalidDefine(ScriptPreprocessorPtr proc, const std::string &define, const std::string &unit_name, size_t line) : PreprocessorError( proc, unit_name, PE_INVALID_DIRECTIVE, "Invalid define '"+define+"'", line ){}
			InvalidDefine(const InvalidDefine& rk) : PreprocessorError(rk){}
		};
	};

};

#endif